/**
 * @author qiao / https://github.com/qiao
 * @author mrdoob / http://mrdoob.com
 * @author alteredq / http://alteredqualia.com/
 * @author WestLangley / http://github.com/WestLangley
 */

import * as THREE from "three";

class EditorControls extends THREE.EventDispatcher {
	constructor(object, domElement) {
		super();

		domElement = ( domElement !== undefined ) ? domElement : document;
		
		// API

		this.enabled = true;
		this.center = new THREE.Vector3();
		this.panSpeed = 0.001;
		this.zoomSpeed = 0.001;
		this.rotationSpeed = 0.005;

		// internals

		var scope = this;
		var vector = new THREE.Vector3();

		var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 };
		var state = STATE.NONE;

		var center = this.center;
		var normalMatrix = new THREE.Matrix3();
		var pointer = new THREE.Vector2();
		var pointerOld = new THREE.Vector2();
		var spherical = new THREE.Spherical();

		// events

		var changeEvent = { type: 'change' };

		this.focus = function (target) {

			var box = new THREE.Box3().setFromObject(target);

			var distance;

			if (box.isEmpty() === false) {

				center.copy(box.getCenter());
				distance = box.getBoundingSphere().radius;

			} else {

				// Focusing on an Group, AmbientLight, etc

				center.setFromMatrixPosition(target.matrixWorld);
				distance = 0.1;

			}

			var delta = new THREE.Vector3(0, 0, 1);
			delta.applyQuaternion(object.quaternion);
			delta.multiplyScalar(distance * 4);

			object.position.copy(center).add(delta);

			scope.dispatchEvent(changeEvent);

		};

		this.pan = function (delta) {

			var distance = object.position.distanceTo(center);

			delta.multiplyScalar(distance * scope.panSpeed);
			delta.applyMatrix3(normalMatrix.getNormalMatrix(object.matrix));

			object.position.add(delta);
			center.add(delta);

			scope.dispatchEvent(changeEvent);

		};

		this.zoom = function (delta) {

			var distance = object.position.distanceTo(center);

			delta.multiplyScalar(distance * scope.zoomSpeed);

			if (delta.length() > distance) return;

			delta.applyMatrix3(normalMatrix.getNormalMatrix(object.matrix));

			object.position.add(delta);

			scope.dispatchEvent(changeEvent);

		};

		this.rotate = function (delta) {

			vector.copy(object.position).sub(center);

			spherical.setFromVector3(vector);

			spherical.theta += delta.x;
			spherical.phi += delta.y;

			spherical.makeSafe();

			vector.setFromSpherical(spherical);

			object.position.copy(center).add(vector);

			object.lookAt(center);

			scope.dispatchEvent(changeEvent);

		};

		// mouse

		function onMouseDown(event) {

			if (scope.enabled === false) return;

			if (event.button === 0) {

				state = STATE.ROTATE;

			} else if (event.button === 1) {

				state = STATE.ZOOM;

			} else if (event.button === 2) {

				state = STATE.PAN;

			}

			pointerOld.set(event.clientX, event.clientY);

			domElement.addEventListener('mousemove', onMouseMove, false);
			domElement.addEventListener('mouseup', onMouseUp, false);
			domElement.addEventListener('mouseout', onMouseUp, false);
			domElement.addEventListener('dblclick', onMouseUp, false);

		}

		function onMouseMove(event) {

			if (scope.enabled === false) return;

			pointer.set(event.clientX, event.clientY);

			var movementX = pointer.x - pointerOld.x;
			var movementY = pointer.y - pointerOld.y;

			if (state === STATE.ROTATE) {

				scope.rotate(new THREE.Vector3(- movementX * scope.rotationSpeed, - movementY * scope.rotationSpeed, 0));

			} else if (state === STATE.ZOOM) {

				scope.zoom(new THREE.Vector3(0, 0, movementY));

			} else if (state === STATE.PAN) {

				scope.pan(new THREE.Vector3(- movementX, movementY, 0));

			}

			pointerOld.set(event.clientX, event.clientY);

		}

		function onMouseUp(event) {

			domElement.removeEventListener('mousemove', onMouseMove, false);
			domElement.removeEventListener('mouseup', onMouseUp, false);
			domElement.removeEventListener('mouseout', onMouseUp, false);
			domElement.removeEventListener('dblclick', onMouseUp, false);

			state = STATE.NONE;

		}

		function onMouseWheel(event) {

			event.preventDefault();

			// if ( scope.enabled === false ) return;

			scope.zoom(new THREE.Vector3(0, 0, event.deltaY));

		}

		function contextmenu(event) {

			event.preventDefault();

		}

		this.dispose = function () {

			domElement.removeEventListener('contextmenu', contextmenu, false);
			domElement.removeEventListener('mousedown', onMouseDown, false);
			domElement.removeEventListener('wheel', onMouseWheel, false);

			domElement.removeEventListener('mousemove', onMouseMove, false);
			domElement.removeEventListener('mouseup', onMouseUp, false);
			domElement.removeEventListener('mouseout', onMouseUp, false);
			domElement.removeEventListener('dblclick', onMouseUp, false);

			domElement.removeEventListener('touchstart', touchStart, false);
			domElement.removeEventListener('touchmove', touchMove, false);

		};

		domElement.addEventListener('contextmenu', contextmenu, false);
		domElement.addEventListener('mousedown', onMouseDown, false);
		domElement.addEventListener('wheel', onMouseWheel, false);

		// touch

		var touches = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
		var prevTouches = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];

		var prevDistance = null;

		function touchStart(event) {

			if (scope.enabled === false) return;

			switch (event.touches.length) {

				case 1:
					touches[0].set(event.touches[0].pageX, event.touches[0].pageY, 0);
					touches[1].set(event.touches[0].pageX, event.touches[0].pageY, 0);
					break;

				case 2:
					touches[0].set(event.touches[0].pageX, event.touches[0].pageY, 0);
					touches[1].set(event.touches[1].pageX, event.touches[1].pageY, 0);
					prevDistance = touches[0].distanceTo(touches[1]);
					break;

			}

			prevTouches[0].copy(touches[0]);
			prevTouches[1].copy(touches[1]);

		}


		function touchMove(event) {

			if (scope.enabled === false) return;

			event.preventDefault();
			event.stopPropagation();

			function getClosest(touch, touches) {

				var closest = touches[0];

				for (var i in touches) {

					if (closest.distanceTo(touch) > touches[i].distanceTo(touch)) closest = touches[i];

				}

				return closest;

			}

			switch (event.touches.length) {

				case 1:
					touches[0].set(event.touches[0].pageX, event.touches[0].pageY, 0);
					touches[1].set(event.touches[0].pageX, event.touches[0].pageY, 0);
					scope.rotate(touches[0].sub(getClosest(touches[0], prevTouches)).multiplyScalar(- scope.rotationSpeed));
					break;

				case 2:
					touches[0].set(event.touches[0].pageX, event.touches[0].pageY, 0);
					touches[1].set(event.touches[1].pageX, event.touches[1].pageY, 0);
					var distance = touches[0].distanceTo(touches[1]);
					scope.zoom(new THREE.Vector3(0, 0, prevDistance - distance));
					prevDistance = distance;


					var offset0 = touches[0].clone().sub(getClosest(touches[0], prevTouches));
					var offset1 = touches[1].clone().sub(getClosest(touches[1], prevTouches));
					offset0.x = - offset0.x;
					offset1.x = - offset1.x;

					scope.pan(offset0.add(offset1).multiplyScalar(0.5));

					break;

			}

			prevTouches[0].copy(touches[0]);
			prevTouches[1].copy(touches[1]);

		}

		domElement.addEventListener('touchstart', touchStart, false);
		domElement.addEventListener('touchmove', touchMove, false);

	}
}

export { EditorControls };